/**
 * 
 */
package cn.softTank.translator;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import org.dom4j.Element;

import cn.softTank.BcpModel.BcpActivity;
import cn.softTank.BcpModel.BcpControl;
import cn.softTank.BcpModel.BcpControlType;
import cn.softTank.BcpModel.BcpEmpty;
import cn.softTank.BcpModel.BcpInvoke;
import cn.softTank.BcpModel.BcpPartnerActivity;
import cn.softTank.BcpModel.BcpPartnerLink;
import cn.softTank.BcpModel.BcpProcess;
import cn.softTank.BcpModel.BcpReceive;
import cn.softTank.BcpModel.BcpReply;
import cn.softTank.BcpModel.BcpVariable;
import cn.softTank.BpelFactory.BpelFactory;
import cn.softTank.BpelFactory.BpelFactoryImpl;
import cn.softTank.BpelModel.Activity;
import cn.softTank.BpelModel.Empty;
import cn.softTank.BpelModel.Flow;
import cn.softTank.BpelModel.Import;
import cn.softTank.BpelModel.Invoke;
import cn.softTank.BpelModel.Links;
import cn.softTank.BpelModel.Namespace;
import cn.softTank.BpelModel.PartnerLink;
import cn.softTank.BpelModel.PartnerLinks;
import cn.softTank.BpelModel.Process;
import cn.softTank.BpelModel.Receive;
import cn.softTank.BpelModel.Reply;
import cn.softTank.BpelModel.Variable;
import cn.softTank.BpelModel.Variables;
import cn.softTank.BpelModel.PartnerLinkType.PartnerLinkType;

/**
 * @author Yuzone (smartzxy@gmail.com)
 * 
 */
public class BcpToBpel {

	protected BpelFactory bpelFactory;
	private Process process;
	private ArrayList<ControlActivity> ctrlList;
	private ArrayDeque<BcpPartnerActivity> activityQueue;
	private Counter count;
	private int ctrlCount;
	private Logger logger;

	public BcpToBpel() {
		if (bpelFactory == null) {
			bpelFactory = new BpelFactoryImpl();
		}
		logger = Logger.getLogger("BcpToBpel");
	}

	public BpelFactory getBpelFactory() {
		return bpelFactory;
	}

	public void setBpelFactory(BpelFactory bpelFactory) {
		this.bpelFactory = bpelFactory;
	}

	public String bcp2Bpel(BcpProcess value) {
		return null;
	}

	public Element bcp2BpelAsElement(BcpProcess value) {

		if (value == null) {
			logger.info("No BcpProcess! ");
			return null;
		}
		if (value.getName() == null) {
			logger.info("Set default name \"process\" for process.");
			value.setName("process");
		}
		if (value.getNameSpace() == null) {
			logger
					.info("Set default namespace \"http://www.njupt.edu.cn\" for process.");
			value.setNameSpace("http://www.njupt.edu.cn");
		}
		process = bpelFactory.createProcess(value.getName(), value
				.getNameSpace());
		if (value.getDocumentation() != null) {
			process.setDocumentation(value.getDocumentation());
		}
		addImport(value);
		if (value.getPartnerLinks() != null) {
			parserPartnerLink(value.getPartnerLinks());
		}
		if (value.getVariables() != null) {
			parserVariable(value.getVariables());
		}
		if (value.getReceive() != null) {
			parserReceive(value.getReceive());
		}
		return process.toElement();
	}

	protected Process addImport(BcpProcess value) {

		List<BcpPartnerLink> pl = value.getPartnerLinks();
		List<BcpVariable> vl = value.getVariables();
		int count = 0;
		// Getting imports from partnerLinks.
		if (pl != null) {
			for (int i = 0; i < pl.size(); i++) {
				PartnerLinkType plt = pl.get(i).getPartnerLinkType();
				List<Import> il = process.getImports();
				boolean isExist = false;
				if (il != null) {
					for (int n = 0; n < il.size(); n++) {
						if (plt.getNameSpace() != null)
							if (il.get(n).getNamespace().equalsIgnoreCase(
									plt.getNameSpace())) {
								isExist = true;
								break;
							}
					}
				}
				if (isExist) {
					continue;
				}
				Import im = bpelFactory
						.createImport("http://schemas.xmlsoap.org/wsdl");
				if (plt != null) {
					im.setNamespace(plt.getNameSpace());
					im.setLocation(plt.getLocation());
				}
				process.addImports(im);
				// Adding namespace definition in process.
				process.addNamespace(new Namespace("ns" + count, im
						.getNamespace()));
				count++;
			}
		}
		// Getting imports from variables.
		if (vl != null) {
			for (int i = 0; i < vl.size(); i++) {
				BcpVariable v = vl.get(i);
				String namespace, location;
				if (v.getElement() != null) {
					namespace = v.getElement().getNameSpace();
					location = v.getElement().getLocation();
				} else if (v.getMessageType() != null) {
					namespace = v.getMessageType().getNameSpace();
					location = v.getMessageType().getLocation();
				} else if (v.getType() != null) {
					namespace = v.getType().getNameSpace();
					location = v.getType().getLocation();
				} else {
					logger.warning("Lost variable type!");
					continue;
				}
				List<Import> il = process.getImports();
				boolean isExist = false;
				if (il != null) {
					for (int n = 0; n < il.size(); n++) {
						if (il.get(n).getNamespace()
								.equalsIgnoreCase(namespace)) {
							isExist = true;
							break;
						}
					}
				}
				if (isExist) {
					continue;
				}
				Import im = bpelFactory
						.createImport("http://schemas.xmlsoap.org/wsdl");
				im.setNamespace(namespace);
				im.setLocation(location);
				process.addImports(im);
				// Adding namespace definition in process.
				process.addNamespace(new Namespace("ns" + count, im
						.getNamespace()));
				count++;
			}
		}
		return process;
	}

	protected Process parserPartnerLink(List<BcpPartnerLink> value) {

		PartnerLinks pls = bpelFactory.createPartnerLinks();
		String name, partnerLinkType;
		for (int i = 0; i < value.size(); i++) {
			BcpPartnerLink p = value.get(i);
			// Using default name for partnerLink.
			if (p.getName() == null || p.getName() == "") {
				name = "partnerLink" + i;
			}
			name = value.get(i).getName();
			// Set partnerLinkType for partnerLink.
			if (p.getPartnerLinkType() == null) {
				logger.warning("Lost partnerLinkType!");
				continue;
			}
			PartnerLinkType plt = p.getPartnerLinkType();
			if (plt.getName() == null) {
				logger.warning("Wrong partnerLinkType!");
				continue;
			}
			partnerLinkType = getPrefix(plt.getNameSpace()) + plt.getName();
			PartnerLink pl = bpelFactory.createPartnerLink(name,
					partnerLinkType);
			pl.setMyRole(p.getMyRole());
			pl.setPartnerRole(p.getPartnerRole());
			pls.addPartnerLink(pl);
		}
		process.setPartnerLinks(pls);
		return process;
	}

	protected Process parserVariable(List<BcpVariable> value) {

		Variables vs = bpelFactory.createVariables();
		for (int i = 0; i < value.size(); i++) {
			BcpVariable bv = value.get(i);
			if (bv.getName() == null) {
				logger.warning("Lost name definition if variable!");
				continue;
			}
			Variable v = bpelFactory.createVariable(bv.getName());
			v.setFrom(bv.getFrom());
			if (bv.getElement() != null) {
				v.setXSDElement(getPrefix(bv.getElement().getNameSpace())
						+ bv.getElement().getName());
			} else if (bv.getMessageType() != null) {
				v.setMessageType(getPrefix(bv.getMessageType().getNameSpace())
						+ bv.getMessageType().getName());
			} else if (bv.getType() != null) {
				v.setType(getPrefix(bv.getType().getNameSpace())
						+ bv.getType().getName());
			} else {
				logger.warning("Lost type definition in variable: "
						+ v.getName());
				continue;
			}
			vs.addVariable(v);
		}
		process.setVariables(vs);
		return process;
	}

	protected Process parserReceive(BcpReceive value) {

		if (value.getInferiors() == null || value.getInferiors().size() == 0) {
			Receive r = bpelFactory.createReceive(value.getPartnerLink(), value
					.getOperation());
			r.setDocumentation(value.getDocumentation());
			r.setCreateInstance(true);
			r.setName(value.getName());
			r.setPortType(value.getPortType());
			if (value.getVariable() != null) {
				r.setVariable(getVariable(value.getVariable()));
			}
			process.setActivity(r);
			return process;
		}
		// If there are more than one activity, use flow construct.
		Flow flow = bpelFactory.createFlow();
		flow.setSuppressJoinFailure(true);
		Links links = bpelFactory.createLinks();
		ctrlList = new ArrayList<ControlActivity>();
		activityQueue = new ArrayDeque<BcpPartnerActivity>();
		// Initial data:
		count = new Counter();
		count.setCount(0);
		activityQueue.offer(value);
		while (activityQueue.size() > 0) {
			flow.addActivity(parserActivity(activityQueue.poll(), links));
		}
		flow.setLinks(links);
		for (ControlActivity ca : ctrlList) {
			flow.addActivity(ca.getAssign());
		}
		process.setActivity(flow);
		return process;
	}

	private String getPrefix(String value) {

		List<Namespace> nsl = process.getNamespace();
		if (value == null)
			return "";
		for (int i = 0; i < nsl.size(); i++) {
			if (value.equalsIgnoreCase(nsl.get(i).getAddress()))
				return nsl.get(i).getPrefix() + ":";
		}
		return "";
	}

	private Variable getVariable(BcpVariable value) {

		List<Variable> vl = process.getVariables().getVariable();
		if (vl != null) {
			for (int i = 0; i < vl.size(); i++) {
				Variable v = vl.get(i);
				if (v.getName().equalsIgnoreCase(value.getName()))
					return v;
			}
		}
		return null;
	}

	private ControlActivity getCtrlActivity(BcpControl bc, Links links) {

		// Return exist ControlActivity object.
		if (ctrlList.size() != 0) {
			for (int i = 0; i < ctrlList.size(); i++) {
				ControlActivity ca = ctrlList.get(i);
				if (ca.getName().equalsIgnoreCase(bc.getName()))
					return ca;
			}
		}
		// Otherwise, create a new one.
		ControlActivity ca = new ControlActivity(bc, links, count);
		// Set name for the new ControlActivity object.
		if (ca.getName() == null || ca.getName() == "") {
			ca.setName("ctrl" + ctrlCount);
			ctrlCount++;
		}
		ctrlList.add(ca);
		List<BcpActivity> list = ca.getInferiors();
		for (int n = 0; n < list.size(); n++) {
			BcpActivity ba = list.get(n);
			if (ba instanceof BcpPartnerActivity) {
				activityQueue.offer((BcpPartnerActivity) ba);
				continue;
			}
			if (ba instanceof BcpControl) {
				BcpControl c = (BcpControl) ba;
				if (c.getType() == BcpControlType.SEQUENCE) {
					ba = getSequence(c);
					if (ba == null) {
						continue;
					}
				}
				BcpEmpty be = new BcpEmpty();
				// Delete ba from its superior node.
				list.remove(n);
				// Add be to ba's superior node.
				list.add(be);
				// Add ba's superior node to be's superiors.
				List<BcpActivity> l = new ArrayList<BcpActivity>();
				l.add(bc);
				be.setSuperiors(l);
				// Add be to activityQueue.
				activityQueue.offer(be);
				// Add ba to be's inferiors.
				l = new ArrayList<BcpActivity>();
				l.add(ba);
				be.setInferiors(l);
				// Delete bc from ba's superiors.
				for (int m = 0; m < ba.getSuperiors().size(); m++) {
					if (bc.getName().equalsIgnoreCase(
							ba.getSuperiors().get(m).getName())) {
						ba.getSuperiors().remove(m);
					}
				}
				// Add be to ba's superiors.
				ba.getSuperiors().add(be);
			}
		}
		return ca;
	}

	private BcpControl getSequence(BcpControl bc) {

		BcpActivity ba = bc.getInferiors().get(0);
		if (ba instanceof BcpPartnerActivity) {
			activityQueue.offer((BcpPartnerActivity) ba);
			return null;
		}
		if (((BcpControl) ba).getType() == BcpControlType.SEQUENCE)
			return getSequence((BcpControl) ba);
		return (BcpControl) ba;

	}

	private Activity parserActivity(BcpPartnerActivity bpa, Links links) {

		Activity pa;
		// This way may destroy polymorphism, but if I modify the subclasses of
		// BcpPartnerActivity, then they may not be recognized by
		// flex :(
		if (bpa instanceof BcpReceive) {
			BcpReceive br = (BcpReceive) bpa;
			Receive re = bpelFactory.createReceive(br.getPartnerLink(), br
					.getOperation());
			re.setPortType(bpa.getPortType());
			if (br.getVariable() != null) {
				re.setVariable(getVariable(br.getVariable()));
			}
			pa = re;
		} else if (bpa instanceof BcpReply) {
			BcpReply br = (BcpReply) bpa;
			Reply rp = bpelFactory.createReply(br.getPartnerLink(), br
					.getOperation());
			rp.setPortType(bpa.getPortType());
			if (br.getVariable() != null) {
				rp.setVariable(getVariable(br.getVariable()));
			}
			pa = rp;
		} else if (bpa instanceof BcpInvoke) {
			BcpInvoke bi = (BcpInvoke) bpa;
			Invoke in = bpelFactory.createInvoke(bi.getPartnerLink(), bi
					.getOperation());
			in.setPortType(bpa.getPortType());
			if (bi.getInputVariable() != null) {
				in.setInputVariable(getVariable(bi.getInputVariable()));
			}
			if (bi.getOutputVariable() != null) {
				in.setOutputVariable(getVariable(bi.getOutputVariable()));
			}
			pa = in;
		} else {
			Empty em = bpelFactory.createEmpty();
			pa = em;
		}
		pa.setName(bpa.getName());
		pa.setDocumentation(bpa.getDocumentation());
		// Add targets to activity.
		if (bpa.getSuperiors() != null) {
			// Each PartnerActivity can only has one in-degree.
			BcpActivity ba = bpa.getSuperiors().get(0);
			if (ba != null && ba instanceof BcpControl) {
				// Get ControlActivity from ctrlList.
				ControlActivity ca = getCtrlActivity((BcpControl) ba, links);
				pa.setTargets(ca.popTargets());
			}
		}
		// Add sources to activity.
		if (bpa.getInferiors() != null) {
			List<BcpActivity> list = bpa.getInferiors();
			pa.setSources(bpelFactory.createSources());
			for (int i = 0; i < list.size(); i++) {
				BcpActivity ba = list.get(i);
				if (ba instanceof BcpControl) {
					BcpControl bcpc = (BcpControl) ba;
					// Get controlActivity from ctrlList.
					ControlActivity ca = getCtrlActivity(bcpc, links);
					// if (ca.getType() == BcpControlType.AND_SPLIT) {
					// for (int n = 0; n < ca.getCount(); n++) {
					// pa.getSources().addSource(ca.popSource());
					// }
					// } else {
					pa.getSources().addSource(ca.popSource());
					// }
				}
			}
		}
		return pa;
	}

}
